/*
 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/Cookie.java,v 1.44 2004/06/05 16:49:20 olegk Exp $
 * $Revision: 531354 $
 * $Date: 2007-04-23 08:53:20 +0200 (Mon, 23 Apr 2007) $
 *
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.httpclient;

import java.io.Serializable;
import java.util.Comparator;
import java.util.Date;

import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.cookie.CookieSpec;
import org.apache.commons.httpclient.util.LangUtils;
import org.apache.commons.logging.Log;

/**
 * <p>
 * HTTP "magic-cookie" represents a piece of state information that the HTTP
 * agent and the target server can exchange to maintain a session.
 * </p>
 * 
 * @author B.C. Holmes
 * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
 * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
 * @author Rod Waldhoff
 * @author dIon Gillard
 * @author Sean C. Sullivan
 * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
 * @author Marc A. Saegesser
 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
 * 
 * @version $Revision: 531354 $ $Date: 2007-04-23 08:53:20 +0200 (Mon, 23 Apr
 *          2007) $
 */
public class Cookie extends NameValuePair implements Serializable, Comparator {

	// ----------------------------------------------------------- Constructors

	/**
	 * Default constructor. Creates a blank cookie
	 */

	public Cookie() {
		this(null, "noname", null, null, null, false);
	}

	/**
	 * Creates a cookie with the given name, value and domain attribute.
	 * 
	 * @param name
	 *            the cookie name
	 * @param value
	 *            the cookie value
	 * @param domain
	 *            the domain this cookie can be sent to
	 */
	public Cookie(String domain, String name, String value) {
		this(domain, name, value, null, null, false);
	}

	/**
	 * Creates a cookie with the given name, value, domain attribute, path
	 * attribute, expiration attribute, and secure attribute
	 * 
	 * @param name
	 *            the cookie name
	 * @param value
	 *            the cookie value
	 * @param domain
	 *            the domain this cookie can be sent to
	 * @param path
	 *            the path prefix for which this cookie can be sent
	 * @param expires
	 *            the {@link Date} at which this cookie expires, or
	 *            <tt>null</tt> if the cookie expires at the end of the session
	 * @param secure
	 *            if true this cookie can only be sent over secure connections
	 * @throws IllegalArgumentException
	 *             If cookie name is null or blank, cookie name contains a
	 *             blank, or cookie name starts with character $
	 * 
	 */
	public Cookie(String domain, String name, String value, String path,
			Date expires, boolean secure) {

		super(name, value);
		// LOG.trace("enter Cookie(String, String, String, String, Date, boolean)");
		if (name == null) {
			throw new IllegalArgumentException("Cookie name may not be null");
		}
		if (name.trim().equals("")) {
			throw new IllegalArgumentException("Cookie name may not be blank");
		}
		this.setPath(path);
		this.setDomain(domain);
		this.setExpiryDate(expires);
		this.setSecure(secure);
	}

	/**
	 * Creates a cookie with the given name, value, domain attribute, path
	 * attribute, maximum age attribute, and secure attribute
	 * 
	 * @param name
	 *            the cookie name
	 * @param value
	 *            the cookie value
	 * @param domain
	 *            the domain this cookie can be sent to
	 * @param path
	 *            the path prefix for which this cookie can be sent
	 * @param maxAge
	 *            the number of seconds for which this cookie is valid. maxAge
	 *            is expected to be a non-negative number. <tt>-1</tt> signifies
	 *            that the cookie should never expire.
	 * @param secure
	 *            if <tt>true</tt> this cookie can only be sent over secure
	 *            connections
	 */
	public Cookie(String domain, String name, String value, String path,
			int maxAge, boolean secure) {

		this(domain, name, value, path, null, secure);
		if (maxAge < -1) {
			throw new IllegalArgumentException("Invalid max age:  "
					+ Integer.toString(maxAge));
		}
		if (maxAge >= 0) {
			setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
		}
	}

	/**
	 * Returns the comment describing the purpose of this cookie, or
	 * <tt>null</tt> if no such comment has been defined.
	 * 
	 * @return comment
	 * 
	 * @see #setComment(String)
	 */
	public String getComment() {
		return cookieComment;
	}

	/**
	 * If a user agent (web browser) presents this cookie to a user, the
	 * cookie's purpose will be described using this comment.
	 * 
	 * @param comment
	 * 
	 * @see #getComment()
	 */
	public void setComment(String comment) {
		cookieComment = comment;
	}

	/**
	 * Returns the expiration {@link Date} of the cookie, or <tt>null</tt> if
	 * none exists.
	 * <p>
	 * <strong>Note:</strong> the object returned by this method is considered
	 * immutable. Changing it (e.g. using setTime()) could result in undefined
	 * behaviour. Do so at your peril.
	 * </p>
	 * 
	 * @return Expiration {@link Date}, or <tt>null</tt>.
	 * 
	 * @see #setExpiryDate(java.util.Date)
	 * 
	 */
	public Date getExpiryDate() {
		return cookieExpiryDate;
	}

	/**
	 * Sets expiration date.
	 * <p>
	 * <strong>Note:</strong> the object returned by this method is considered
	 * immutable. Changing it (e.g. using setTime()) could result in undefined
	 * behaviour. Do so at your peril.
	 * </p>
	 * 
	 * @param expiryDate
	 *            the {@link Date} after which this cookie is no longer valid.
	 * 
	 * @see #getExpiryDate
	 * 
	 */
	public void setExpiryDate(Date expiryDate) {
		cookieExpiryDate = expiryDate;
	}

	/**
	 * Returns <tt>false</tt> if the cookie should be discarded at the end of
	 * the "session"; <tt>true</tt> otherwise.
	 * 
	 * @return <tt>false</tt> if the cookie should be discarded at the end of
	 *         the "session"; <tt>true</tt> otherwise
	 */
	public boolean isPersistent() {
		return (null != cookieExpiryDate);
	}

	/**
	 * Returns domain attribute of the cookie.
	 * 
	 * @return the value of the domain attribute
	 * 
	 * @see #setDomain(java.lang.String)
	 */
	public String getDomain() {
		return cookieDomain;
	}

	/**
	 * Sets the domain attribute.
	 * 
	 * @param domain
	 *            The value of the domain attribute
	 * 
	 * @see #getDomain
	 */
	public void setDomain(String domain) {
		if (domain != null) {
			int ndx = domain.indexOf(":");
			if (ndx != -1) {
				domain = domain.substring(0, ndx);
			}
			cookieDomain = domain.toLowerCase();
		}
	}

	/**
	 * Returns the path attribute of the cookie
	 * 
	 * @return The value of the path attribute.
	 * 
	 * @see #setPath(java.lang.String)
	 */
	public String getPath() {
		return cookiePath;
	}

	/**
	 * Sets the path attribute.
	 * 
	 * @param path
	 *            The value of the path attribute
	 * 
	 * @see #getPath
	 * 
	 */
	public void setPath(String path) {
		cookiePath = path;
	}

	/**
	 * @return <code>true</code> if this cookie should only be sent over secure
	 *         connections.
	 * @see #setSecure(boolean)
	 */
	public boolean getSecure() {
		return isSecure;
	}

	/**
	 * Sets the secure attribute of the cookie.
	 * <p>
	 * When <tt>true</tt> the cookie should only be sent using a secure protocol
	 * (https). This should only be set when the cookie's originating server
	 * used a secure protocol to set the cookie's value.
	 * 
	 * @param secure
	 *            The value of the secure attribute
	 * 
	 * @see #getSecure()
	 */
	public void setSecure(boolean secure) {
		isSecure = secure;
	}

	/**
	 * Returns the version of the cookie specification to which this cookie
	 * conforms.
	 * 
	 * @return the version of the cookie.
	 * 
	 * @see #setVersion(int)
	 * 
	 */
	public int getVersion() {
		return cookieVersion;
	}

	/**
	 * Sets the version of the cookie specification to which this cookie
	 * conforms.
	 * 
	 * @param version
	 *            the version of the cookie.
	 * 
	 * @see #getVersion
	 */
	public void setVersion(int version) {
		cookieVersion = version;
	}

	/**
	 * Returns true if this cookie has expired.
	 * 
	 * @return <tt>true</tt> if the cookie has expired.
	 */
	public boolean isExpired() {
		return (cookieExpiryDate != null && cookieExpiryDate.getTime() <= System
				.currentTimeMillis());
	}

	/**
	 * Returns true if this cookie has expired according to the time passed in.
	 * 
	 * @param now
	 *            The current time.
	 * 
	 * @return <tt>true</tt> if the cookie expired.
	 */
	public boolean isExpired(Date now) {
		return (cookieExpiryDate != null && cookieExpiryDate.getTime() <= now
				.getTime());
	}

	/**
	 * Indicates whether the cookie had a path specified in a path attribute of
	 * the <tt>Set-Cookie</tt> header. This value is important for generating
	 * the <tt>Cookie</tt> header because some cookie specifications require
	 * that the <tt>Cookie</tt> header should only include a path attribute if
	 * the cookie's path was specified in the <tt>Set-Cookie</tt> header.
	 * 
	 * @param value
	 *            <tt>true</tt> if the cookie's path was explicitly set,
	 *            <tt>false</tt> otherwise.
	 * 
	 * @see #isPathAttributeSpecified
	 */
	public void setPathAttributeSpecified(boolean value) {
		hasPathAttribute = value;
	}

	/**
	 * Returns <tt>true</tt> if cookie's path was set via a path attribute in
	 * the <tt>Set-Cookie</tt> header.
	 * 
	 * @return value <tt>true</tt> if the cookie's path was explicitly set,
	 *         <tt>false</tt> otherwise.
	 * 
	 * @see #setPathAttributeSpecified
	 */
	public boolean isPathAttributeSpecified() {
		return hasPathAttribute;
	}

	/**
	 * Indicates whether the cookie had a domain specified in a domain attribute
	 * of the <tt>Set-Cookie</tt> header. This value is important for generating
	 * the <tt>Cookie</tt> header because some cookie specifications require
	 * that the <tt>Cookie</tt> header should only include a domain attribute if
	 * the cookie's domain was specified in the <tt>Set-Cookie</tt> header.
	 * 
	 * @param value
	 *            <tt>true</tt> if the cookie's domain was explicitly set,
	 *            <tt>false</tt> otherwise.
	 * 
	 * @see #isDomainAttributeSpecified
	 */
	public void setDomainAttributeSpecified(boolean value) {
		hasDomainAttribute = value;
	}

	/**
	 * Returns <tt>true</tt> if cookie's domain was set via a domain attribute
	 * in the <tt>Set-Cookie</tt> header.
	 * 
	 * @return value <tt>true</tt> if the cookie's domain was explicitly set,
	 *         <tt>false</tt> otherwise.
	 * 
	 * @see #setDomainAttributeSpecified
	 */
	public boolean isDomainAttributeSpecified() {
		return hasDomainAttribute;
	}

	/**
	 * Returns a hash code in keeping with the {@link Object#hashCode} general
	 * hashCode contract.
	 * 
	 * @return A hash code
	 */
	public int hashCode() {
		int hash = LangUtils.HASH_SEED;
		hash = LangUtils.hashCode(hash, this.getName());
		hash = LangUtils.hashCode(hash, this.cookieDomain);
		hash = LangUtils.hashCode(hash, this.cookiePath);
		return hash;
	}

	/**
	 * Two cookies are equal if the name, path and domain match.
	 * 
	 * @param obj
	 *            The object to compare against.
	 * @return true if the two objects are equal.
	 */
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (this == obj)
			return true;
		if (obj instanceof Cookie) {
			Cookie that = (Cookie) obj;
			return LangUtils.equals(this.getName(), that.getName())
					&& LangUtils.equals(this.cookieDomain, that.cookieDomain)
					&& LangUtils.equals(this.cookiePath, that.cookiePath);
		} else {
			return false;
		}
	}

	/**
	 * Return a textual representation of the cookie.
	 * 
	 * @return string.
	 */
	public String toExternalForm() {
		CookieSpec spec = null;
		if (getVersion() > 0) {
			spec = CookiePolicy.getDefaultSpec();
		} else {
			spec = CookiePolicy.getCookieSpec(CookiePolicy.NETSCAPE);
		}
		return spec.formatCookie(this);
	}

	/**
	 * <p>
	 * Compares two cookies to determine order for cookie header.
	 * </p>
	 * <p>
	 * Most specific should be first.
	 * </p>
	 * <p>
	 * This method is implemented so a cookie can be used as a comparator for a
	 * SortedSet of cookies. Specifically it's used above in the
	 * createCookieHeader method.
	 * </p>
	 * 
	 * @param o1
	 *            The first object to be compared
	 * @param o2
	 *            The second object to be compared
	 * @return See {@link java.util.Comparator#compare(Object,Object)}
	 */
	public int compare(Object o1, Object o2) {
		// LOG.trace("enter Cookie.compare(Object, Object)");

		if (!(o1 instanceof Cookie)) {
			throw new ClassCastException(o1.getClass().getName());
		}
		if (!(o2 instanceof Cookie)) {
			throw new ClassCastException(o2.getClass().getName());
		}
		Cookie c1 = (Cookie) o1;
		Cookie c2 = (Cookie) o2;
		if (c1.getPath() == null && c2.getPath() == null) {
			return 0;
		} else if (c1.getPath() == null) {
			// null is assumed to be "/"
			if (c2.getPath().equals(CookieSpec.PATH_DELIM)) {
				return 0;
			} else {
				return -1;
			}
		} else if (c2.getPath() == null) {
			// null is assumed to be "/"
			if (c1.getPath().equals(CookieSpec.PATH_DELIM)) {
				return 0;
			} else {
				return 1;
			}
		} else {
			return c1.getPath().compareTo(c2.getPath());
		}
	}

	/**
	 * Return a textual representation of the cookie.
	 * 
	 * @return string.
	 * 
	 * @see #toExternalForm
	 */
	public String toString() {
		return toExternalForm();
	}

	// ----------------------------------------------------- Instance Variables

	/** Comment attribute. */
	private String cookieComment;

	/** Domain attribute. */
	private String cookieDomain;

	/** Expiration {@link Date}. */
	private Date cookieExpiryDate;

	/** Path attribute. */
	private String cookiePath;

	/** My secure flag. */
	private boolean isSecure;

	/**
	 * Specifies if the set-cookie header included a Path attribute for this
	 * cookie
	 */
	private boolean hasPathAttribute = false;

	/**
	 * Specifies if the set-cookie header included a Domain attribute for this
	 * cookie
	 */
	private boolean hasDomainAttribute = false;

	/** The version of the cookie specification I was created from. */
	private int cookieVersion = 0;

	// -------------------------------------------------------------- Constants

	/** Log object for this class */
	// private static final Log LOG = LogFactory.getLog(Cookie.class);

}
